﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using Red.Controls.Depot.FileViewItemsProviders;
using Red.Controls.Depot.ViewModels;
using Red.Controls.Shell;
using Red.Controls.ViewModels;
using Red.Core;
using Red.Core.Extensions;

namespace Red.Controls.Depot
{
    /// <summary>
    ///     Two panel depot files browser
    /// </summary>
    public partial class DepotBrowser : UserControl
    {
        #region Data

        private DepotInfo _depot;
        private List<FileSystemWatcher> _watchers = new List<FileSystemWatcher>();
        private bool _isOpeningNewTab;
        private bool _isRefreshingWholeView = false;

        #endregion Data

        #region Properties

        public DepotFileViewPage ActivePage
        {
            get { return _tabSwitcher.ActivePage as DepotFileViewPage; }
            set { _tabSwitcher.ActivePage = value; }
        }

        public DepotInfo Depot
        {
            get { return _depot; }

            set 
            {
                if ( _depot != value )
                {
                    _depot = value;
                    _tree.Depot = _depot;
                    if ( ActivePage != null )
                    {
                        ActivePage.Depot = _depot; // NOTE: should be set before ItemsProvider
                        ActivePage.ItemsProvider = new EmptyItemsProvider();
                    }

                    RegisterWatcherEvents();

                    _breadcrumb.ClearHistory();
                }
            }
        }

        public bool ShowAssetView
        {
            get { return _tree.ShowAssetView; }

            set
            {
                if ( _tree.ShowAssetView != value )
                {
                    _tree.ShowAssetView = value;
                }
            }
        }

        public IEnumerable<string> Favorites
        {
            get { return _tree.Favorites; }

            set { _tree.Favorites = value; }
        }

        public IEnumerable<string> Tabs
        {
            get { return _tabSwitcher.Content.Select( tab => ( tab as DepotFileViewPage ).Path ); }
        }

        public int ActiveTabIndex
        {
            get { return _tabSwitcher.ActivePageIndex; }
            private set { _tabSwitcher.ActivePageIndex = value; }
        }

        /// <summary>
        ///     Full path to the selected item
        /// </summary>
        public string SelectedPath
        {
            get { return _tree.SelectedItem != null ? _tree.SelectedItem.Path : null; }
        }

        #endregion Properties

        #region C-tor

        public DepotBrowser()
        {
            InitializeComponent();
            _tabSwitcher.Content.CollectionChanged += TabSwitcher_CollectionChanged;
        }

        #endregion C-tor

        #region Public methods

        /// <summary>
        ///     Re-reads depot tree and file view content
        /// </summary>
        public async Task RefreshView()
        {
            if ( ActivePage != null )
            {
                ActivePage.RefreshView();
            }
            _isRefreshingWholeView = true; // prevents from refreshing ActivePage again when tree selection is restored
            await _tree.Refresh();
        }

        /// <summary>
        ///     Sets both a dir in the tree and a file on the files page
        /// </summary>
        public void SelectByPath( string path )
        {
            string absPath = _depot.GetAbsolutePath( path );
            var dir = Path.GetDirectoryName( absPath );
            if ( _tree.SelectByPath( dir ) ) // changes file view in the active files page
            {
                if ( ActivePage != null )
                {
                    ActivePage.SelectByPath( absPath );
                }
            }
        }


        /// <summary>
        ///     Clears all previously opened tabs, opens new tabs and sets one of them as active.
        ///     If one of tabs cannot be opened and is on the left activeTabIndex tab, activeTabIndex is being corrected to select correct page despite missing ones.
        ///     If previously active tab is missing, first of the tabs becomes an active one.
        /// </summary>
        /// <param name="tabs">Array of tabs to open.</param>
        /// <param name="activeTabIndex">Index of tab that should be activated after opening all tabs.</param>
        public void LoadTabs( string[] tabs, int activeTabIndex )
        {
            // clear any previously opened tabs
            if ( _tabSwitcher.Content.Any() )
                _tabSwitcher.Content.Clear();

            if ( tabs == null )
                return;

            for ( var i = 0; i < tabs.Length; ++i )
            {
                var path = tabs[ i ];
                var item = _tree.FindItemByPath( path );

                if ( item == null )
                {
                    // path not found in tree
                    if ( i < activeTabIndex )
                    {
                        // tab on the left of previously active tab is missing, we need to decrement active tab index
                        --activeTabIndex;
                    }
                    else if ( i == activeTabIndex )
                    {
                        // previously active tab is missing, select first one
                        activeTabIndex = 0;
                    }
                }
                else
                {
                    // path found in tree, open in tab and refresh
                    OpenTab( path );
                    RefreshTab( item );
                }
            }

            _tabSwitcher.ActivePageIndex = activeTabIndex;

            if ( activeTabIndex >= 0 ) // in case there is no tabs
                _tree.SelectByPath( tabs[ activeTabIndex ] );
        }

        #endregion Public methods

        #region Events

        public class ResourceDoubleClickEventArgs : EventArgs
        {
            public FileViewItem Item;
        }

        public delegate void ResourceDoubleClickEventHandler( object sender, ResourceDoubleClickEventArgs e );

        public event ResourceDoubleClickEventHandler ResourceDoubleClick;
        
        #endregion Events

        #region Event handlers

        private void Tree_SelectedItemChanged( object sender, RoutedPropertyChangedEventArgs<object> e )
        {
            var selected = e.NewValue as DepotTreeItem;
            if ( selected != null ) 
            {
                if ( _isOpeningNewTab || ActivePage == null )
                {
                    // no tabs opened, open new one for selected tree item
                    OpenTab( selected.Path );
                }

                if ( !_isRefreshingWholeView )
                {
                    RefreshTab( selected );
                }
            }
            _isRefreshingWholeView = false;
        }

        private void TabSwitcher_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
        {
            // do not allow to close the last page
            _tabSwitcher.ShowCloseButtons = _tabSwitcher.Content.Count > 1;
        }

        private void RefreshTab( DepotTreeItem item )
        {
            ActivePage.Path = item.Path;
            ActivePage.BreadcrumbPath = item.BreadcrumbPath;
            ActivePage.ItemsProvider = item.GetItemsProvider();
            ActivePage.Title = item.ShortName;
            _breadcrumb.Path = ActivePage.BreadcrumbPath;
        }

        private void Files_ItemDoubleClick( object sender, FileViewItem item )
        {
            Debug.Assert( item != null );
            if ( item is FileViewItemDirectory ) 
            {
                _isOpeningNewTab = Keyboard.IsKeyDown( Key.LeftCtrl ) || Keyboard.IsKeyDown( Key.RightCtrl );
                _tree.SelectByPath( item.Path ); // will trigger refreshing of the rest
            }
            else if ( item is FileViewItemResource ) 
            {
                ResourceDoubleClick?.Invoke( this, new ResourceDoubleClickEventArgs() { Item = item } );

            }
        }

        private void Files_SelectedItemChanged( object sender, IList<FileViewItem> items )
        {
            if ( items.Count == 0 )
            {
                _descriptionText.Text = "<no selection>";
            }
            else if ( items.Count == 1 )
            {
                _descriptionText.Text = items[0].Description;
                _previewImage.Source = items[0].Image;
            }
            else
            {
                _descriptionText.Text = "<multiple items selected>";
            }
        }

        private void Breadcrumb_NavigateTo( object sender, BreadcrumbBarItem e )
        {
            _tree.SelectByPath( e.Path ); // will trigger refreshing of the rest
        }

        private async Task<List<string>> Breadcrumb_EnumerateBranches( BreadcrumbBarItem e )
        {
            DepotTreeItem item = _tree.FindItemByPath( e.Path );
            if ( item != null )
            {
                await item.BuildAsync(); // ensure it's built
                var names = from child in item.Children select child.Title;
                return names.ToList();
            }
            else
                return new List<string>();
        }

        private void BrowseBack_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            _breadcrumb.HistoryBack();
        }

        private void BrowseForward_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            _breadcrumb.HistoryForward();
        }

        private async void RefreshView_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            await RefreshView();
        }

        private void OpenInNewTab_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            var directory = e.Parameter as FileViewItemDirectory;

            _isOpeningNewTab = true;
            _tree.SelectByPath( directory.Path );
        }

        private void OpenInNewTab_CanExecute( object sender, CanExecuteRoutedEventArgs e )
        {
            e.CanExecute = e.Parameter is FileViewItemDirectory;
        }

        private void CloseTab_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            _tabSwitcher.Content.Remove( ActivePage );
        }

        private void CloseTab_CanExecute( object sender, CanExecuteRoutedEventArgs e )
        {
            // always keep one open tab
            e.CanExecute = _tabSwitcher.Content.Count > 1;
        }

        private void TabSwitcher_ActivePageChanged( object sender, RoutedEventArgs e )
        {
            // ActivePage will be null if we removed page that was active
            if ( ActivePage == null )
                return;

            _breadcrumb.Path = ActivePage.BreadcrumbPath;
            _tree.SelectByPath( ActivePage.Path );
        }

        #endregion Event handlers

        #region Private methods

        private async void RegisterWatcherEvents()
        {
            UnregisterWatcherEvents();

            foreach ( DepotInfo.EntryPoint entry in _depot.EntryPoints )
            {
                try
                {
                    FileSystemWatcher watcher = new FileSystemWatcher();

                    watcher.Path = Path.Combine( _depot.RootPath, entry.Path );
                    watcher.Filter = "";
                    watcher.NotifyFilter = NotifyFilters.DirectoryName;
                    watcher.Deleted += OnDirectoryChanged;
                    watcher.Created += OnDirectoryChanged;
                    watcher.Renamed += OnDirectoryRenamed;
                    watcher.IncludeSubdirectories = true;
                    watcher.EnableRaisingEvents = true;

                    _watchers.Add( watcher );
                }
                catch ( ArgumentException )
                {
                    await RedMessageBox.ShowAsync( this, RedDialogType.Ok, string.Format( "Failed to register watcher on directory '{0}', check if it exists.", entry.Path ), "Error" );
                }
            }
        }

        private void UnregisterWatcherEvents()
        {
            foreach ( FileSystemWatcher watcher in _watchers )
            {
                watcher.Deleted -= OnDirectoryChanged;
                watcher.Created -= OnDirectoryChanged;
                watcher.Renamed -= OnDirectoryRenamed;
            }

            _watchers.Clear();
        }

        private void DispatchRebuild()
        {
            Dispatcher.Invoke( DispatcherPriority.Background, new Action(
                () =>
                {
                    RefreshView().DoNotAwait();
                } ) );
        }

        private void OnDirectoryChanged(object sender, FileSystemEventArgs e)
        {
            // it has to be dispatched as it comes from another thread
            DispatchRebuild();
        }

        private void OnDirectoryRenamed(object source, RenamedEventArgs e)
        {
            // it has to be dispatched as it comes from another thread
            DispatchRebuild();
        }

        private void OpenTab( string path )
        {
            var filePage = new DepotFileViewPage { Depot = _depot, Path = path };
            filePage.ItemDoubleClick += Files_ItemDoubleClick;
            filePage.SelectedItemChanged += Files_SelectedItemChanged;
            _tabSwitcher.Content.Add( filePage );

            ActivePage = filePage;
            _isOpeningNewTab = false;
        }

        #endregion Private methods
    }
}
    